package rssApp.mediators 
{
	import com.bit101.components.CheckBox;
	import com.bit101.components.Component;
	import com.bit101.components.HBox;
	import com.bit101.components.InputText;
	import com.bit101.components.Panel;
	import com.bit101.components.ProgressBar;
	import com.bit101.components.PushButton;
	import com.bit101.components.ScrollPane;
	import common.events.PropertyEvent;
	import common.IComponentBuilder;
	import common.IDisposable;
	import flash.display.DisplayObject;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import flash.utils.Dictionary;
	import rss.events.RssFeedEvent;
	import rss.IRssEntryModel;
	import rssApp.builders.RssAppItemComponentBuilder;
	import rssApp.components.RssAppItemComponent;
	import rssApp.enums.RssAppEventEnum;
	import rssApp.events.RssAppCategoryEvent;
	import rssApp.events.RssAppFeedEvent;
	import rssApp.IRssAppModel;
	import rssApp.IRssAppViewMediator;
	import rssApp.models.RssAppModel;
	
	 /// @eventType = rssApp.events.RssAppFeedEvent.LOAD
	[Event(name = "load", type = "rssApp.events.RssAppFeedEvent")]

	
	/**
	 * ...
	 * @author Luke Van In
	 */
	public class RssAppViewMediator implements IRssAppViewMediator
	{
		
		
		// private variables
		private var _eventDispatcher:IEventDispatcher;
		
		private var _model:IRssAppModel;
		
		private var _stage:Stage;
		
		private var _categoriesPanel:Panel;
		
		private var _contentScrollPane:ScrollPane;
		
		private var _addressInput:InputText;
		
		private var _loadButton:PushButton;

		private var _itemsLayout:Component;
		
		private var _categoriesLayout:Component;
		
		private var _contentLayout:Component;
		
		private var _progressbar:ProgressBar;
		
		
		
	
		
		
		
		
		/**
		 * Constructor
		 * 
		 * @param	eventDispatcher		Global event router
		 * @param	model				Application model
		 * @param	stage		
		 * @param	addressInput		
		 * @param	loadButton
		 * @param	contentLayout
		 * @param	categoriesPanel
		 * @param	contentScrollPane
		 * @param	itemsLayout
		 * @param	categoriesLayout
		 * @param	progressbar
		 */
		public function RssAppViewMediator(eventDispatcher:IEventDispatcher, model:IRssAppModel, stage:Stage, addressInput:InputText, loadButton:PushButton, contentLayout:Component, categoriesPanel:Panel, contentScrollPane:ScrollPane, itemsLayout:Component, categoriesLayout:Component, progressbar:ProgressBar) 
		{
			this._eventDispatcher = eventDispatcher;
			
			this._model = model;
			
			this._stage = stage;
			
			this._categoriesPanel = categoriesPanel;
			
			this._contentScrollPane = contentScrollPane;
			
			this._itemsLayout = itemsLayout;
			
			this._categoriesLayout = categoriesLayout;
			
			this._addressInput = addressInput;
			
			this._loadButton = loadButton;
			
			this._contentLayout = contentLayout;
			
			this._progressbar = progressbar;
			
			
			this.update();
			
			this.addEvents();
		}
		
		
		
		
		/**
		 * Creates event listeners for objects which affect the view.
		 */
		private function addEvents():void
		{
			this._stage.addEventListener(Event.RESIZE, this.stageResizeHandler);
			
			this._loadButton.addEventListener(MouseEvent.CLICK, this.loadClickHandler);
			
			this._model.addEventListener(PropertyEvent.CHANGE, this.modelPropertyChangeHandler);			
		}
		
		
		
		/**
		 * Removes event listeners (called by dispose).
		 */
		private function removeEvents():void
		{
			this._stage.removeEventListener(Event.RESIZE, this.stageResizeHandler);
			
			this._loadButton.removeEventListener(MouseEvent.CLICK, this.loadClickHandler);
			
			this._model.removeEventListener(PropertyEvent.CHANGE, this.modelPropertyChangeHandler);			
		}
		
		
		
		/**
		 * Handle stage resize events.
		 * Updates the view.
		 * 
		 * @param	e
		 */
		private function stageResizeHandler(e:Event):void
		{
			this.update();
		}
		
		
		
		
		
		/**
		 * Re-calculates and re-positions items according to the size of the stage and specific items being displayed, specifically:
		 * 		-	Sizes the address bar to span the width of the window  (allowing for the load button)
		 * 		- 	Sizes the categories panel to a fixed with, and the height of the window (allowing for the address bar)
		 * 		- 	Sizes the contents panel to fill the remaining area
		 * 		- 	Sizes feed items (if any) to fit into the content panel
		 * 		- 	Ensures that the contents panel is only visible if there are items to be displayed
		 * 		-	Ensures that the category panel is only visible if there are categories to be displayed
		 */
		private function update():void
		{
			var w:Number = this._stage.stageWidth;
			
			var h:Number = this._stage.stageHeight;
			

			// address bar
			this._addressInput.setSize(w - this._loadButton.width, this._loadButton.height);
			
			
			// content and categories panels
			if (this._contentLayout.contains(this._contentScrollPane))
				this._contentLayout.removeChild(this._contentScrollPane);
			
			if (this._contentLayout.contains(this._categoriesPanel))
				this._contentLayout.removeChild(this._categoriesPanel);
			
				
			// categories
			if (this._categoriesLayout.numChildren > 0)
			{
				this._contentLayout.addChild(this._categoriesPanel);
				
				this._categoriesPanel.setSize(100, h - this._addressInput.height);
			}
			

			// content items
			if (this._itemsLayout.numChildren > 0)
			{
				this._contentLayout.addChild(this._contentScrollPane);
			
				this._contentScrollPane.setSize(w - this._categoriesPanel.width, h - this._addressInput.height);
				
				for (var i:int = 0; i < this._itemsLayout.numChildren; i++)
				{
					var item:Component = this._itemsLayout.getChildAt(i) as Component;
					
					item.width = this._contentScrollPane.width - 11;
					
					item.draw();
				}
				
				this._itemsLayout.draw();
				
				this._contentScrollPane.update();
				
				this._contentScrollPane.draw();
			}
			
			
		}
		
		
		
		
		
		
		
		/**
		 * Handles the "load feed" button being clicked.
		 * Dispatches an event to load the feed from a specific URL.
		 * 
		 * @param	e
		 */
		private function loadClickHandler(e:MouseEvent):void
		{
			var event:RssAppFeedEvent = new RssAppFeedEvent(RssAppEventEnum.LOAD_FEED, this._addressInput.text);
			
			this._eventDispatcher.dispatchEvent(event);
		}
		
		
		
		
	
		

		/**
		 * Called when a property of the model changes.
		 * Updates the view depending on the property which changes, specifically:
		 * 		-	Categories property (eg: when selecting/deselecting a checkbox): Re-creates the categories view and the feed items
		 * 		- 	Feed property (eg: when loading a feed): Re-creates the feed items
		 * 
		 * @param	e
		 */
		private function modelPropertyChangeHandler(e:PropertyEvent):void
		{
			
			switch (e.property)
			{
				// CATEGORIES
				case RssAppModel.CATEGORIES:
					this.updateCategories();
					this.updateItems();
					break;			

					
				// FEED
				case RssAppModel.FEED:
					this.updateItems();
					break;
			}
		}
		
		
		
		/**
		 * Updates categories:
		 * 		- 	Remove any existing category checkboxes.
		 * 		- 	Creates a checkbox for each category. Checkboxes are created 
		 * 			ticked or unticked depending on the status of the category 
		 * 			in the model.
		 * 
		 */
		private function updateCategories():void
		{
			this.disposeCategories();
			
			this.createCategories(this._model.categories);
			
			this.update();
		}
		
		
		
		/**
		 * Removes and disposes category checkboxes.
		 */
		private function disposeCategories():void
		{
			while (this._categoriesLayout.numChildren > 0)
			{
				var checkbox:CheckBox = this._categoriesLayout.removeChildAt(0) as CheckBox;
				
				checkbox.dispose();
			}
		}
		
		
		
		/**
		 * Creates a checkbox for each category and adds it to the categories 
		 * layout component. 
		 * 
		 * @param	categories		A dictionary where the key is a string with 
		 * 							the name of the category, and the value is 
		 * 							a boolean indicating if the category is 
		 * 							selected or not.
		 */
		private function createCategories(categories:Dictionary):void
		{
			for (var category:String in categories)
			{
				var component:Component = this.createCategoryCheckbox(category, categories[category]);
				
				this._categoriesLayout.addChild(component);
			}
		}
		
		
		
		/**
		 * Creates and returns a category checkbox vased 
		 * 
		 * @param	category		The name of the category
		 * @param	isSelected		True if the category is selected (and the 
		 * 							checkbox is ticked), or false otherwise.
		 * @return
		 */
		private function createCategoryCheckbox(category:String, isSelected:Boolean):CheckBox
		{
			var component:CheckBox = new CheckBox(this._categoriesLayout, 0, 0, category, this.categoryCheckboxHandler);
			
			component.selected = isSelected;
			
			return component;
		}
		
		
		
		/**
		 * Handles checkbox click event.
		 * Dispatches an event to select or unselect the category depending on 
		 * the state of the checkbox.
		 * 
		 * @param	e
		 */
		private function categoryCheckboxHandler(e:Event):void
		{
			var component:CheckBox = e.currentTarget as CheckBox;
			
			var type:String = component.selected ? RssAppEventEnum.SELECT_CATEGORY : RssAppEventEnum.UNSELECT_CATEGORY;
			
			var event:Event = new RssAppCategoryEvent(type, component.label);
			
			this._eventDispatcher.dispatchEvent(event);
		}
		
		
		
		
		/**
		 * Updates feed entry items by removing any existing feed items, then
		 * creating a new feed item component for each feed entry in the 
		 * application model.
		 * 
		 */
		private function updateItems():void
		{
			this.disposeItems();
			
			this._contentScrollPane.resetScrollPosition();
			
			if (this._model.feed != null)
				this.createItems(this._model.feed.entries, this._model.categories); // TODO: refactor (violating law of demeter here)
			
			this.update();
		}
		
		
		
		
		/**
		 * Removes and disposes all feed item components from the view.
		 */
		private function disposeItems():void
		{
			while (this._itemsLayout.numChildren > 0)
			{
				var item:RssAppItemComponent = this._itemsLayout.removeChildAt(0) as RssAppItemComponent;
				
				item.dispose();
			}
		}
		
		
		
		/**
		 * Creates a feed item component for each feed entry in the application 
		 * model, and adds it to the feed items layout. Only items which are 
		 * in a visible category are created (ie, a category which has a tick in its 
		 * corresponding checkbox). If no category checkboxes are ticked then no
		 * feed items appear in the view. 
		 * 
		 * @param	entries
		 * @param	categories
		 */
		private function createItems(entries:Vector.<IRssEntryModel>, categories:Dictionary):void
		{
			for (var i:int = 0; i < entries.length; i++)
			{
				var entry:IRssEntryModel = entries[i];
				
				if (!categories[entry.category])
					continue;
					
				var item:Component = this.createItemComponent(entry);
			
				this._itemsLayout.addChild(item);
			}
		}
		
		
		
		/**
		 * Creates and returns a feed item component populated from a feed entry 
		 * model.
		 * 
		 * @see 	RssAppItemComponentBuilder
		 * @param	entry
		 * @return
		 */
		private function createItemComponent(entry:IRssEntryModel):Component
		{
			var builder:IComponentBuilder = new RssAppItemComponentBuilder(this._eventDispatcher, this._itemsLayout, entry);
			
			var item:Component = builder.buildComponent();
			
			return item;
		}
		
		
		
		
		/**
		 * 
		 */
		public function dispose():void 
		{
			this.removeEvents();
		
			this.disposeItems();	
			
			this.disposeCategories();
		}
		
	}

}